Completed
Push — master ( f1da37...c68c1b )
by Mark
15s queued 11s
created

Model.arrayToObjects   A

Complexity

Conditions 4

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
import HasManyThrough from './Model/Relation/HasManyThrough';
2
import HasMany from "./Model/Relation/HasMany";
3
import BelongsTo from "./Model/Relation/BelongTo";
4
import MorphOne from "./Model/Relation/MorphOne";
5
import HasOne from "./Model/Relation/HasOne";
6
import HasOneThrough from "./Model/Relation/HasOneThrough";
7
import MorphTo from "./Model/Field/MorphTo";
8
import Field from "./Model/Field";
9
import Relation from "./Model/Relation";
10
import ForeignKey from "./Model/Field/ForeignKey";
11
import Index from "./Table/Index";
12
import {ModelInterface, ModelStaticInterface} from "../JeloquentInterfaces";
13
import Collection from "./Collection";
14
import * as Str from "../Util/Str";
15
import * as Obj from "../Util/Obj";
16
17
class Model implements ModelInterface {
18
19
    private static kebabCaseName: string;
20
21
    private static snakeCaseName: string;
22
23
    _tmpId: string;
24
25
    ['constructor']: ModelStaticInterface;
26
27
    private _originalFields: Array<Field>;
28
29
    private _primaryFields: Array<Field>;
30
31
    private numberOfFields: number;
32
33
    constructor(fields: Array<Field> = []) {
34
        this.setFields(this.addRelationFields(fields));
35
        this._tmpId = `_${++globalThis.Store.numberOfModelCreated}`;
36
    }
37
38
    static get className() : string {
39
        return this.name;
40
    }
41
42
    static get kebabCaseClassName(): string {
43
        return this.kebabCaseName ??= Str.KebabCase(this.className);
44
    }
45
46
    static get snakeCaseClassName(): string {
47
        return this.snakeCaseName ??= Str.SnakeCase(this.className);
48
    }
49
50
    get className(): string {
51
        return this.constructor.className;
52
    }
53
54
    get dirtyFieldNames() {
55
        return this.dirtyFields.map(field => field.name);
56
    }
57
58
    get dirtyFields() {
59
        return this.originalFields.filter(field => field.isDirty);
60
    }
61
62
    get kebabCaseClassName(): string {
63
        return this.constructor.kebabCaseClassName;
64
    }
65
66
    get originalFields(): Array<Field> {
67
        return this._originalFields;
68
    }
69
70
    get originalPrimaryKey() {
71
        return this.primaryFields.reduce((toValue, field, i) => {
72
            if (i > 0) {
73
                return `${toValue}-${field.originalValue}`;
74
            }
75
            return field.originalValue;
76
        }, '') ?? this._tmpId ?? null;
77
    }
78
79
    get originalValues() {
80
        return this.originalFields.reduce((originalValues, field) => {
81
            if (field.originalValue !== undefined) {
82
                originalValues[field.name] = field.originalValue;
83
            }
84
            return originalValues;
85
        }, {});
86
    }
87
88
    get primaryFields():Array<Field> {
89
        return this._primaryFields ??= this.originalFields.filter(field => field.isPrimary);
90
    }
91
92
    get primaryKey(): string|number {
93
        return this.primaryFields.reduce((toValue:string, field:Field, i:number): string|number => {
94
            if (i > 0) {
95
                return `${toValue}-${field.value}`;
96
            }
97
            return field.value as (string|number);
98
        }, '') ?? this._tmpId ?? null;
99
    }
100
101
    get primaryKeyName(): Array<string> {
102
        return this.originalFields.filter(field => field.isPrimary).map(field => field.name);
103
    }
104
105
    get snakeCaseClassName(): string {
106
        return this.constructor.snakeCaseClassName;
107
    }
108
109
    static aSyncInsert(data): Promise<Collection> {
110
        return new Promise((resolve) => {
111
            queueMicrotask(() => {
112
                resolve(this.insert(data));
113
            });
114
        });
115
    }
116
117
    static aSyncUpdate(data): Promise<Collection> {
118
        return new Promise((resolve) => {
119
            queueMicrotask(() => {
120
                resolve(this.update(data));
121
            });
122
        });
123
    }
124
125
    static all(): Collection {
126
        return globalThis.Store.database().all(this.className);
127
    }
128
129
    static delete(id): void {
130
        globalThis.Store.database().delete(this.className, id);
131
    }
132
133
    static find(id) {
134
        return globalThis.Store.database().find(this.className, id);
135
    }
136
137
    static getIndexByKey(indexName) {
138
        return globalThis
139
            .Store
140
            .database()
141
            .getIndexByKey(this.className, indexName);
142
    }
143
144
    static getInstance(): ModelInterface {
145
        const original = globalThis.Store.classInstances[this.className] ?? (globalThis.Store.classInstances[this.className] = new this())
146
        const fieldsClone = original.originalFields.reduce((obj, field) => {
147
            obj.push(Object.assign(Object.create(Object.getPrototypeOf(field)), field));
148
            return obj;
149
        }, [])
150
151
        return Object.create(Object.getPrototypeOf(original)).setFields(fieldsClone);
152
    }
153
154
    static ids() {
155
        return globalThis
156
            .Store
157
            .database()
158
            .ids(this.className);
159
    }
160
161
    static insert(data: object|Array<object>): Collection {
162
        const modelsData = Array.isArray(data) ? data : [data];
163
        const length = modelsData.length;
164
        const models = new Collection();
165
        for (let i = 0; i < length; i++) {
166
            const modelData = modelsData[i];
167
            const model = this.getInstance();
168
            model.fill(modelData);
169
            globalThis.Store.database().insert(this.className, model);
170
            model.fillRelations(modelData);
171
            models.push(model);
172
        }
173
        return models;
174
    }
175
176
    static registerIndex(name: string): void {
177
        Index.register(this.getInstance(), name);
178
    }
179
180
    /**
181
     * @deprecated
182
     */
183
    static select(id) {
184
        try {
185
            return globalThis.Store.database().select(this.className, id);
186
        } catch (e) {
187
            console.error(e);
188
        }
189
    }
190
191
    static update(data: object|Array<object>): Collection {
192
        const modelsData = Array.isArray(data) ? data : [data];
193
        const length = modelsData.length;
194
        const models = new Collection();
195
        for (let i = 0; i < length; i++) {
196
            const model = this.getInstance();
197
            model.fill(data);
198
            globalThis.Store.database().update(this.className, model);
199
            model.fillRelations(data);
200
            models.push(model);
201
        }
202
        return models;
203
    }
204
205
    addRelationFields(fields) {
206
        const fieldList = [...fields];
207
        fields.forEach((field, i) => {
208
            if (field instanceof Relation) {
209
                fieldList.splice(i, 0, ...field.getRelationalFields());
210
            }
211
        });
212
213
        this.numberOfFields = fieldList.length;
214
        return fieldList;
215
    }
216
217
    delete() {
218
        this.constructor.delete(this.primaryKey);
219
    }
220
221
    fill(data) {
222
        for (let i = 0; i < this.numberOfFields; i++) {
223
            if (!(this.originalFields[i] instanceof Relation)) {
224
                const fieldName = this.originalFields[i].name;
225
                if (data[fieldName] !== undefined) {
226
                    this[`_${fieldName}`] = data[fieldName];
227
                }
228
            }
229
        }
230
    }
231
232
    fillRelations(data: object): void {
233
        // insert through relations after model insert;
234
        for (let i = 0; i < this.numberOfFields; i++) {
235
            if ((this.originalFields[i] instanceof Relation)) {
236
                const fieldName = this.originalFields[i].name;
237
                if (data[fieldName] !== undefined) {
238
                    this[`_${fieldName}`] = data[fieldName];
239
                }
240
            }
241
        }
242
    }
243
244
    isDirty(fieldName) {
245
        if (fieldName) {
246
            return this.dirtyFieldNames.includes(fieldName);
247
        }
248
        return this.dirtyFields.length > 0;
249
    }
250
251
    jsonStringify(): string {
252
        return JSON.stringify(this.toObject());
253
    }
254
255
    registerIndex(name) {
256
        Index.register(this, name);
257
    }
258
259
    resetDirty() {
260
        this.originalFields.filter((field) => !(field instanceof Relation)).forEach(field => {
261
            field.resetDirty();
262
        })
263
    }
264
265
    save() {
266
        const className = this.className;
267
        const currentDatabase = globalThis.Store.database();
268
        const tableIds = currentDatabase.ids(className);
269
270
        if (this.primaryKey[0] !== '_' && tableIds.includes(this._tmpId)) {
271
            //todo remove indexes for foreignKey
272
            //                                team_id  this.team_id
273
            Index.removeTmpIdFromIndex(this);
274
            currentDatabase.delete(className, this._tmpId);
275
        }
276
277
        if (tableIds.includes(this.primaryKey)) {
278
            currentDatabase.update(className, this);
279
            return;
280
        }
281
        currentDatabase.insert(className, this);
282
    }
283
284
    setFields(fields) {
285
        this._originalFields = [...fields];
286
        this.numberOfFields = this.originalFields.length;
287
        for (let i = 0; i < this.numberOfFields; i++) {
288
            this.originalFields[i].setup(this);
289
        }
290
        return this;
291
    }
292
293
    tableSetup(table) {
294
        for (let i = 0; i < this.numberOfFields; i++) {
295
            if (this.originalFields[i] instanceof ForeignKey) {
296
                this.originalFields[i].tableSetup(table);
297
            }
298
299
            if (this.originalFields[i] instanceof HasManyThrough) {
300
                this.originalFields[i].tableSetup(table);
301
            }
302
        }
303
    }
304
305
    toJSON(): object {
306
        return this.toObject();
307
    }
308
309
    toObject(fromRelation = false): object {
310
        return Obj.fromModel(this, fromRelation);
311
    }
312
}
313
314
export {
315
    Model,
316
    Field,
317
    Relation,
318
    BelongsTo,
319
    HasOne,
320
    HasOneThrough,
321
    HasMany,
322
    HasManyThrough,
323
    MorphOne,
324
    MorphTo,
325
    ForeignKey,
326
};